/*
* Copyright 2014 WANdisco
*
* WANdisco licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package c5db.log;
import c5db.interfaces.log.SequentialEntryCodec;
import c5db.log.generated.OLogHeader;
import com.google.common.collect.Iterables;
import com.google.common.io.CountingInputStream;
import io.protostuff.Schema;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.util.List;
import static c5db.log.EntryEncodingUtil.decodeAndCheckCrc;
import static c5db.log.EntryEncodingUtil.encodeWithLengthAndCrc;
import static c5db.log.LogPersistenceService.BytePersistence;
import static c5db.log.LogPersistenceService.PersistenceNavigator;
import static c5db.log.LogPersistenceService.PersistenceNavigatorFactory;
import static c5db.log.LogPersistenceService.PersistenceReader;
/**
* A SequentialLog of OLogEntry, together with an OLogHeader message. Together, these two
* objects represent the byte contents present on a single BytePersistence encoded by a
* QuorumDelegatingLog.
*/
class SequentialLogWithHeader {
private static final Schema<OLogHeader> HEADER_SCHEMA = OLogHeader.getSchema();
private static final SequentialEntryCodec<OLogEntry> CODEC = new OLogEntry.Codec();
public final SequentialLog<OLogEntry> log;
public final OLogHeader header;
/**
* Private constructor; use one of the public static factory methods below.
*/
private SequentialLogWithHeader(SequentialLog<OLogEntry> log, OLogHeader header) {
this.log = log;
this.header = header;
}
/**
* Create a new instance by reading in data from a preexisting BytePersistence. It
* reads the header and checks its CRC, then creates a SequentialLog to represent
* the entries.
*
* @param persistence A BytePersistence representing an existing log (at least an
* OLogHeader and zero or more OLogEntry)
* @param navigatorFactory Used to create a PersistenceNavigator required for the log
* @return A new SequentialLogWithHeader instance
* @throws java.io.IOException
*/
public static SequentialLogWithHeader readLogFromPersistence(BytePersistence persistence,
PersistenceNavigatorFactory navigatorFactory)
throws IOException {
HeaderWithSize headerWithSize = readHeaderFromPersistence(persistence);
return create(persistence, navigatorFactory, headerWithSize);
}
/**
* Create a new log and header and write them to a new persistence. The header corresponds to the
* current position and state of the current log (if there is one).
*
* @param persistenceService The LogPersistenceService, needed to append the new persistence to
* @param navigatorFactory Factory to create the PersistenceNavigator required for the log
* @param header OLogHeader to write immediately at the start of the persistence
* @param quorumId Quorum ID, needed to determine where to append the persistence
* @param <P> The type of the BytePersistence to create and write
* @throws java.io.IOException
*/
public static <P extends BytePersistence> SequentialLogWithHeader writeNewLog(
LogPersistenceService<P> persistenceService, PersistenceNavigatorFactory navigatorFactory,
OLogHeader header, String quorumId) throws IOException {
final P persistence = persistenceService.create(quorumId);
HeaderWithSize headerWithSize = writeHeaderToPersistence(persistence, header);
persistenceService.append(quorumId, persistence);
return create(persistence, navigatorFactory, headerWithSize);
}
/**
* Create a PersistenceNavigator for data resident on an existing persistence.
*
* @param persistence A BytePersistence representing an existing log (at least an
* OLogHeader and zero or more entries)
* @param navigatorFactory Factory to create the PersistenceNavigator with
* @param entryCodec Codec to use to decode entries on the persistence
* @return A new PersistenceNavigator, ready to use with the log
* @throws IOException
*/
public static PersistenceNavigator createNavigatorFromPersistence(BytePersistence persistence,
PersistenceNavigatorFactory navigatorFactory,
SequentialEntryCodec<?> entryCodec)
throws IOException {
HeaderWithSize headerWithSize = readHeaderFromPersistence(persistence);
return createNavigatorForHeader(persistence, navigatorFactory, entryCodec, headerWithSize);
}
private static PersistenceNavigator createNavigatorForHeader(BytePersistence persistence,
PersistenceNavigatorFactory navigatorFactory,
SequentialEntryCodec<?> entryCodec,
HeaderWithSize headerWithSize)
throws IOException {
final PersistenceNavigator navigator = navigatorFactory.create(persistence, entryCodec, headerWithSize.size);
navigator.addToIndex(headerWithSize.header.getBaseSeqNum() + 1, headerWithSize.size);
return navigator;
}
private static SequentialLogWithHeader create(BytePersistence persistence,
PersistenceNavigatorFactory navigatorFactory,
HeaderWithSize headerWithSize)
throws IOException {
final PersistenceNavigator navigator =
createNavigatorForHeader(persistence, navigatorFactory, CODEC, headerWithSize);
final SequentialLog<OLogEntry> log = new EncodedSequentialLog<>(persistence, CODEC, navigator);
return new SequentialLogWithHeader(log, headerWithSize.header);
}
private static HeaderWithSize readHeaderFromPersistence(BytePersistence persistence) throws IOException {
try (CountingInputStream input = getCountingInputStream(persistence.getReader())) {
final OLogHeader header = decodeAndCheckCrc(input, HEADER_SCHEMA);
final long headerSize = input.getCount();
return new HeaderWithSize(header, headerSize);
}
}
private static HeaderWithSize writeHeaderToPersistence(BytePersistence persistence, OLogHeader header)
throws IOException {
final List<ByteBuffer> serializedHeader = encodeWithLengthAndCrc(HEADER_SCHEMA, header);
persistence.append(Iterables.toArray(serializedHeader, ByteBuffer.class));
return new HeaderWithSize(header, persistence.size());
}
private static CountingInputStream getCountingInputStream(PersistenceReader reader) {
return new CountingInputStream(Channels.newInputStream(reader));
}
private static class HeaderWithSize {
public final OLogHeader header;
public final long size;
private HeaderWithSize(OLogHeader header, long size) {
this.header = header;
this.size = size;
}
}
}